/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package lime2;
import lights.interfaces.*;
import java.util.*;

/**
 * The primary class used by application programmers. Lime programming basically is getting a reference to 
 * LimeTupleSpace objects and manipulating them. 
 * <br>
 * Please note that Lime protects itself against "wrong" arguments passed to it; IllegalArgumentExceptions are thrown. 
 * Parameter checking is enforced by:
 * <P>
 * &#9679 constructors of the (public) classes that are instantiated by users (LimeTemplate and Reaction)
 * <br>
 * &#9679 methods of LimeTupleSpace  
 * </P>
 * In order to understand why it is sufficient to patrol these points, let us consider the <u>flows of data</u>.
 * <br>
 * <br>
 * <b><i>Users to Lime</i></b>
 * <br>
 * Application programmers can pass data to the Lime system in the following ways: 
 * <P>
 * &#9679 instantiating LimeTemplates 
 * <br>
 * &#160 &#160 .. for the LimeTupleSpace <i>in</i>, <i>inp</i>, <i>ing</i>, <i>rd</i>, <i>rdp</i>, <i>rdg</i> operations 
 * <br>
 * &#160 &#160 .. for creating Reactions
 * <br>
 * &#9679 instantiating Reactions 
 * <br>
 * &#160 &#160 .. for the LimeTupleSpace <i>addStrongReaction</i> operation
 * <br>
 * &#160 &#160 .. for the LimeTupleSpace <i>addWeakReaction</i> operation 
 * <br>
 * &#9679 performing the LimeTupleSpace operations <i>out</i>/<i>outg</i> of ITuple(s)
 * </P> 
 * <b><i>Lime to Users</i></b>
 * <br>
 * <br>
 * The following ways are used by the Lime system to return data to the application programmers:
 * <P>
 * &#9679 LimeTuples returned as the results of the LimeTupleSpace operations
 * <br>
 * &#9679 LimeTuples returned as part of ReactionEvents, within reaction listeners
 * <br>
 * &#9679 ReactionIDs returned because of the installation of Reactions
 * </P>
 * <b>Notes for Proper Usage</b>
 * <br>
 * &#9679 not all Strings are <i>legal</i> tuplespace names: a valid name must contain only Unicode letters or digits
 * (see comment <i>Naming</i> in class TupleSpaceProxy)
 * <br>
 * &#9679 before a reference to a LimeTupleSpace can be acquired, we impose the LimeServer to be already booted (if 
 * not, an exception is thrown). This isn't really necessary if programmers need only the "local" features of
 * LimeTupleSpace (i.e., no communication with other hosts). But this guarantees that inner Lime components can rely 
 * on a booted LimeServer.  
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini
 */

public class LimeTupleSpace 
{		
 // this hashtable[String,LimeTupleSpace] keeps track of the existing LimeTupleSpaces for this host 
 private static Hashtable tuplespaces = new Hashtable(); 

 // this (long) global counter is used to enforce uniqueness of the numeric identifiers for the TupleIDs
 private static long tids = -1;

 // the LocalOpMgr of the LimeTupleSpace
 private LocalOpMgr lop;
 
 // the name of the LimeTupleSpace  
 private String name;
 
 // the policy of the LimeTupleSpace
 private byte policy;
 
 // private constructor
 private LimeTupleSpace(String name, byte policy) 
 {
  super();
  // registers the name
  this.name = name;
  // registers the policy
  this.policy = policy;
  // creates the LocalOpMgr
  this.lop = new LocalOpMgr(name, policy);
  // starts the LocalOpMgr thread
  this.lop.start();
  // if this host is engaged then new tuplespaces must be shared 
  if (LimeServer.getServer().isEngaged()) CommunicationMgr.get().getCA().joinGroup(name);
 }
 
 /**
  * Creates a new LimeTupleSpace with a given name, if no LimeTupleSpace with that name already exists. Otherwise 
  * returns the existing LimeTupleSpace, thus <u>forcing agents to share it</u>. The policy is set to default.
  * <br>
  * Please note that not all String are <u>legal</u> tuplespace names: a valid name must contain only Unicode letters
  * or digits; see comment <i>Naming</i> in class TupleSpaceProxy.
  * <br>
  * Also note that <u>before a reference</u> to a LimeTupleSpace can be acquired, we impose the LimeServer to be 
  * already booted (if not, an exception is thrown). This isn't really necessary if programmers need only the "local" 
  * features of LimeTupleSpace (i.e., no communication with other hosts). But this guarantees that LocalOpMgr, 
  * LimeTSMgr, CommunicationMgr, etc.. can rely on a booted LimeServer.  
  * <br>
  * This method must be <u>synchronized</u>: testing proved synchronization to be necessary if multiple agents using 
  * the same tuplespace are loaded at the same time. Consider the following scenario:
  * <P>
  * &#9679 the thread of agent A asks for tuplespace X
  * <br>
  * &#9679 the tuplespace is returned to A but before it is added to the hashtable agent B executes
  * <br>
  * &#9679 the thread of agent B asks for tuplespace X and a new object is returned to it
  * </P>
  * Thus A and B get different instances of tuplespace X. Synchronization prevents this "very common" case of 
  * interleaving from happening.  
  * 
  * @param name of the LimeTupleSpace we want to use
  * @return LimeTupleSpace
  * @throws BootException
  * @throws NamingException
  */ 
 synchronized public static LimeTupleSpace get(String name) 
 {
  if (!LimeServer.getServer().isBooted()) throw new BootException("server has not been booted");	
  if (name.equals(LimeConstants.SYSTEM_NAME)) throw new NamingException("name \"" + name + "\" is reserved");
  
  LimeTupleSpace ret = null;
  if (name != null)
  {	
   ret = (LimeTupleSpace) tuplespaces.get(name);
   if (ret == null) 
   {
    ret = new LimeTupleSpace(name, Policies.DEFAULT);
    tuplespaces.put(name, ret);
   }
  }
  return ret;
 }
 
 /**
  * Same as the previous "get" method; but this one allows to set policy of the LimeTupleSpace at creation time. 
  * Please note that if the LimeTupleSpace already exists with a different policy, a PolicyException is thrown. 
  * Lime doesn't force consensus on a policy for the tuplespaces of a federation: it is the application programmer
  * (.. 's agent/s) that must select the same policy on every host (.. and this makes sense). 
  * 
  * @param name of the LimeTupleSpace we want to use
  * @param pol policy we want to apply to the LimeTupleSpace
  * @return LimeTupleSpace
  * @throws BootException
  * @throws NamingException
  * @throws PolicyException
  */
 synchronized public static LimeTupleSpace get(String name, byte pol) throws PolicyException
 {
  if (!LimeServer.getServer().isBooted()) throw new BootException("server has not been booted");	
  if (name.equals(LimeConstants.SYSTEM_NAME)) throw new NamingException("name \"" + name + "\" is reserved");
    
  LimeTupleSpace ret = null;
  if (name != null)
  {	
   ret = (LimeTupleSpace) tuplespaces.get(name);
   if (ret == null) 
   {
    ret = new LimeTupleSpace(name, pol);
    tuplespaces.put(name, ret);
   }
   else if (pol != ret.policy) throw new PolicyException("policy has already been set to " + Translator.translatePolicy(ret.policy));
  }
  return ret;
 }
 
 /** 
  * Returns the policy for this LimeTupleSpace. 
  *
  * @return policy 
  */
 public byte getPolicy()
 {
  return policy;	
 }
 
 /**
  * Returns the next TupleID for this host. 
  * <br>
  * The numeric identifier of the first TupleID created on this host is zero. Synchronization is necessary since 
  * multiple agents (threads) could try to access the global counter ("tids") when we are in the middle of a previous 
  * invocation of this method (.. this would ruin uniqueness of tuple identifiers). 
  * 
  * @return valid TupleID
  */
 public synchronized static TupleID nextTID() 
 {
  tids++;	
  return new TupleID(Location.HERE.getID(), tids);	
 } 
  
 /**
  * Returns the name of this LimeTupleSpace.
  * 
  * @return name
  */   
 public String getName() 
 { 
  return name; 
 }   

 /**
  * Returns the names of all the LimeTupleSpaces for this host.
  * 
  * @return array of names
  */
 public static String[] getNames()
 {
  String[] ret = new String[tuplespaces.size()];
  int i = 0;
  for (Enumeration e = tuplespaces.keys(); e.hasMoreElements();) ret[i++] = (String) e.nextElement();
  return ret;
 }
 
 /**
  * Reads a tuple matching a specific LimeTemplate. 
  * 
  * @param t LimeTemplate
  * @return LimeTuple
  */   
 public LimeTuple rd(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");
  
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.RD, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  return LimeTuple.delimify((ITuple) result.get());
 }    
   
 /**
  * Non-blocking read of a tuple matching a specific LimeTemplate.
  * 
  * @param t LimeTemplate
  * @return LimeTuple
  */   
 public LimeTuple rdp(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.RDP, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  return LimeTuple.delimify((ITuple) result.get());
 } 
 
 /**
   * Non-blocking read of a group of tuples matching a specific LimeTemplate.
   *  
   * @param t LimeTemplate
   * @return array of LimeTuples
   */ 
 public LimeTuple[] rdg(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.RDG, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  ITuple[] ret = (ITuple[]) result.get();
  if (ret == null) return new LimeTuple[0]; 
  LimeTuple[] ltret = new LimeTuple[ret.length];
  for (int i = 0; i < ret.length; i++) ltret[i] = LimeTuple.delimify(ret[i]);
  return ltret;
 }  
    
 /** 
  * Takes a tuple matching a specific LimeTemplate.
  * 
  * @param t LimeTemplate
  * @return LimeTuple
  */
 public LimeTuple in(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.IN, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  return LimeTuple.delimify((ITuple) result.get());	
 }  
   
 /**
  * Non-blocking take of a tuple matching a specific LimeTemplate.
  * 
  * @param t LimeTemplate
  * @return LimeTuple
  */   
 public LimeTuple inp(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.INP, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  return LimeTuple.delimify((ITuple) result.get());
 }    
 
 /** 
  * Non-blocking take of a group of tuples matching a specific LimeTemplate.
  * 
  * @param t LimeTemplate
  * @return array of LimeTuples
  */
 public LimeTuple[] ing(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.ING, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  ITuple[] ret = (ITuple[]) result.get();
  if (ret == null) return null; 
  LimeTuple[] ltret = new LimeTuple[ret.length];
  for (int i = 0; i < ret.length; i++) ltret[i] = LimeTuple.delimify(ret[i]);
  return ltret;
 }  
 
 /**
  * Out of a tuple. The destination is this host.
  * 
  * @param t ITuple (not limified, just a normal ITuple)
  * @return corresponding LimeTuple
  */ 
 public LimeTuple out(ITuple t)
 {	
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");
 	
  LimeTuple lt = new LimeTuple(nextTID(), Location.HERE, Location.HERE, t);	
  TupleOp to = new TupleOp(LimeConstants.OUT, new ITuple[] {lt.limify()}, new Result());
  lop.postOperation(to);
  return lt;
 } 
  
 /**
  * Out of a tuple for a specific destination Location. 
  * <br>
  * The destination location cannot be UNSPECIFIED.
  * 
  * @param t ITuple (not limified, just a normal ITuple)
  * @param whereto destination Location
  * @return corresponding LimeTuple
  * @throws InvalidLocationException
  */ 
 public LimeTuple out(Location whereto, ITuple t) 
 {
  // parameter checking	
  if (whereto == null || t == null) throw new IllegalArgumentException("null value");	
  if (whereto.isUnspecified()) throw new InvalidLocationException("destination cannot be unspecified");		

  LimeTuple lt = new LimeTuple(nextTID(), Location.HERE, whereto, t);	
  TupleOp to = new TupleOp(LimeConstants.OUT, new ITuple[] {lt.limify()}, new Result());
  lop.postOperation(to);
  return lt;
 }  
 
 /**
  * Out of a tuple. The destination is this host.
  * 
  * @param t LimeTuple object 
  * @return corresponding LimeTuple
  */ 
 public LimeTuple out(LimeTuple t) {
	// parameter checking	
	if (t == null) throw new IllegalArgumentException("null value");
	TupleOp to = new TupleOp(LimeConstants.OUT, new ITuple[] {t.limify()}, new Result());
	lop.postOperation(to);
	return t;
 }

 /**  
  * Out of a group of tuples for a specific destination Location. 
  * <br>
  * The destination location cannot be UNSPECIFIED.
  * 
  * @param t the array of ITuples (not limified, just normal ITuples)
  * @param whereto destination Location
  * @return corresponding LimeTuples
  * @throws InvalidLocationException
  */ 
 public LimeTuple[] outg(Location whereto, ITuple[] t)
 {
  // parameter checking	
  if (whereto == null || testNullArray(t)) throw new IllegalArgumentException("null value"); 	
  if (whereto.isUnspecified()) throw new InvalidLocationException("destination cannot be unspecified");	
 
  LimeTuple[] lt = new LimeTuple[t.length];
  ITuple[] lim = new ITuple[t.length]; 
  for (int i = 0; i < t.length; i++)
  { 
   lt[i] = new LimeTuple(nextTID(), Location.HERE, whereto, t[i]); 
   lim[i] = lt[i].limify();  
  } 
  TupleOp to = new TupleOp(LimeConstants.OUTG, lim, new Result());
  lop.postOperation(to);
  return lt;
 }
 
 /**
  * The outg which accepts LimeTuple
  * @param ts
  * @return
  * @author Xing Jiankuan
  */
 public LimeTuple[] outg(LimeTuple[] ts) {
	 // parameter checking	
	if (testNullArray(ts))
		throw new IllegalArgumentException("null value");
	for (int i = 0; i < ts.length; i++) {
		if (ts[i].getDestination().equals(Location.UNSPECIFIED))
			throw new IllegalArgumentException(
					"The destination must be set explicitly");
	}
	ITuple[] lim = new ITuple[ts.length];
	for (int i = 0; i < ts.length; i++) {	
		lim[i] = ts[i].limify();
	}
	TupleOp to = new TupleOp(LimeConstants.OUTG, lim, new Result());
	lop.postOperation(to);
	return ts;
 }
  
 /**  
  * Out of groups of tuples for specific destination Locations. 
  * <br>
  * A destination location cannot be UNSPECIFIED.
  * 
  * @param t different groups of ITuples (not limified, just normal ITuples)
  * @param whereto destinations for the groups of ITuples
  * @return corresponding LimeTuples
  * @throws InvalidLocationException
  */ 
 public LimeTuple[] outg(Location[] whereto, ITuple[][] t)
 {
  // parameter checking	
  if (testNullArray(whereto) || testNullArray(t)) throw new IllegalArgumentException("null value"); 	
  if (whereto.length != t.length) throw new IllegalArgumentException("lenghts of the given arrays must be the same");
  for (int z = 0; z < t.length; z++)
  {
   if (whereto[z].isUnspecified()) throw new InvalidLocationException("destination cannot be unspecified");	
   if (testNullArray(t[z])) throw new IllegalArgumentException("null value for tuple or array of tuples"); 
  } 
   
  int n = 0;
  for (int z = 0; z < t.length; z++) n += t[z].length;
  LimeTuple[] lt = new LimeTuple[n];
  ITuple[] lim = new ITuple[n]; 
  int k = -1;
  for (int j = 0; j < t.length; j++)
  for (int i = 0; i < t[j].length; i++)
  { 
   k++;	
   lt[k] = new LimeTuple(nextTID(), Location.HERE, whereto[j], t[j][i]); 
   lim[k] = lt[k].limify();  
  } 
  
  TupleOp to = new TupleOp(LimeConstants.OUTG, lim, new Result());
  lop.postOperation(to);
  return lt;
 }   
 
 /**
  * Installs strong reactions on this host. 
  * <br>
  * A strong reaction cannot be remote or ubiquitous: it can regard only the local (= this host) projection of the 
  * tuplespace; but it is guaranteed that strong reactions are fired atomically wrt being enabled (see the Lime 
  * papers). You can install a strong reaction on a remote host using an agent.
  * 
  * @param reactions strong reactions to install
  * @return corresponding reaction ids
  * @throws InvalidLocationException
  */ 
 public ReactionID[] addStrongReaction(Reaction[] reactions)
 {
  // parameter checking	
  if (testNullArray(reactions)) throw new IllegalArgumentException("null value");	
  for (int i = 0; i < reactions.length; i++) if (!reactions[i].getLimeTemplate().getCurrent().isHere()) throw new InvalidLocationException("strong reactions cannot be remote or ubiquitous"); 

  // end of check
  Result result = new Result(); 	
  ReactionOp ro = new ReactionOp(LimeConstants.INSTALL_STRONG, reactions, result);	
  lop.postOperation(ro);
  return (ReactionID[]) result.get();
 } 
 
 /**
  * Removes strong reactions from this host. 
  * <br>
  * If the ReactionIDs objects do not correspond to strong reactions installed previously on this host, the reactions 
  * will not be deregistered. You can uninstall a strong reaction from a remote host using an agent (.. you still need
  * the ReactionID "handle").
  * 
  * @param ids of the (registered) reactions to remove
  * @return boolean[], where <code>boolean[i]</code> is <code>true</code> if reaction <code>i</code> was successfully
  *         removed, <code>false</code> otherwise
  */ 
 public boolean[] removeStrongReaction(ReactionID[] ids) 
 {
  // parameter checking	
  if (testNullArray(ids)) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result(); 	
  ReactionOp ro = new ReactionOp(LimeConstants.REMOVE_STRONG, ids, result);	
  lop.postOperation(ro);
  return (boolean[]) result.get();
 }  
 
 /**
  * Installs weak reactions on this host. 
  * <br>
  * A weak reaction can be ubiquitous but firing of the reaction / being enabled are no longer guaranteed to be a 
  * single atomic step (see the Lime papers). You can install a weak reaction on a remote host using an agent.
  * 
  * @param reactions weak reactions to install
  * @return corresponding reaction ids
  */ 
 public ReactionID[] addWeakReaction(Reaction[] reactions)
 {
  // parameter checking	
  if (testNullArray(reactions)) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  ReactionOp ro = new ReactionOp(LimeConstants.INSTALL_WEAK, reactions, result);
  lop.postOperation(ro);
  return (ReactionID[]) result.get();
 } 
 
 /**
  * Removes weak reactions from this host. 
  * <br>
  * If the ReactionIDs objects do not correspond to weak reactions installed previously on this host, the reactions 
  * will not be deregistered. You can uninstall a weak reaction from a remote host using an agent (.. you still need
  * the ReactionID "handle").
  * 
  * @param ids of the (registered) reactions to remove
  * @return boolean[], where <code>boolean[i]</code> is <code>true</code> if reaction <code>i</code> was successfully
  *         removed, <code>false</code> otherwise
  */ 
 public boolean[] removeWeakReaction(ReactionID[] ids) 
 {
  // parameter checking	
  if (testNullArray(ids)) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result(); 	
  ReactionOp ro = new ReactionOp(LimeConstants.REMOVE_WEAK, ids, result);	
  lop.postOperation(ro);
  return (boolean[]) result.get();
 }  

 /** 
  * Creates a shallow copy of an ITuple object. 
  * <br>
  * If you modify an IField of the copy, then you modify the corresponding IField of the original tuple and 
  * vice-versa.
  * 
  * @param t the ITuple we want to be cloned
  * @return copy of t
  */
 public static ITuple copy(ITuple t)
 {
  if (t == null) return null;	
  return copy(t, 0, t.length()); 
 }

 /**
  * Creates a shallow copy of an ITuple containing only a subrange of the fields of the original tuple. 
  * If you modify an IField of the copy, then you modify the corresponding IField of the original tuple. 
  * If you modify an IField of the original tuple, then you modify the corresponding IField of the copy only if it 
  * falls in the subrange.
  *
  * @param t ITuple to copy
  * @param start start copying from this index 
  * @param end end copying at this index
  * @return copy (restricted to the IFields in the range) 
  */
 public static ITuple copy(ITuple t, int start, int end) 
 {
  ITuple toReturn = null;
  // if the tuple is null we don't bother
  if (t != null)  
  {
   // we have to look for the right constructor because of tuplespace adapters	
   try { toReturn = (ITuple) t.getClass().newInstance(); }
   catch (InstantiationException ie) { throw new LimeRuntimeException("can't instantiate ITuple", ie); }
   catch (IllegalAccessException iae) { throw new LimeRuntimeException("can't access tuple class", iae); }
   if (toReturn != null) for (int x = start; x < end; x++) toReturn.add(t.get(x)); 
  }
  return toReturn;
 }

 /** 
  * Creates a deep copy of an ITuple object. If you modify the copy, then you don't modify the original tuple and 
  * vice-versa.
  * 
  * @param t the ITuple we want to be deep copied
  * @return deep copy of t
  */
 public static ITuple deepCopy(ITuple t)
 {
  if (t == null) return null;	
  return (deserializeITuple(serializeITuple(t)));	
 }
 
 /** 
  * Serializes an ITuple. 
  *
  * @param t ITuple to serialize
  * @return array of <code>byte</code>
  */
 static byte[] serializeITuple(ITuple t) 
 {
  return Serializer.serialize(t); 
 }

 /**
  * Deserializes a serialized ITuple. 
  *  
  * @param bytes serialized ITuple
  * @return ITuple
  */   
 static ITuple deserializeITuple(byte[] bytes) 
 {
  return (ITuple) Serializer.deserialize(bytes);
 }

 // parameter checking function: returns true if the given array is null or contains at least one null element
 private static boolean testNullArray(Object[] o)
 {
  if (o == null) return true;
  for (int i = 0; i < o.length; i++) if (o[i] == null) return true;
  return false;
 }

 /**
  * This method is provided only because of LimeHTTP.
  * <br>
  * Returns textual representation of all the registered reactions. Please note that since we don't trust classes 
  * outside the core of Lime, we return only textual representation of internal "critical" objects.
  *
  * @return textual representation of the reactions 
  */
 String[] getTextualRegisteredReactions()
 {
  return lop.getTextualRegisteredReactions();
 } 
 
}
